package com.nx.platform.es.system.config;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.Yaml;

import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 配置中心管理类
 */
public class ConfigCenter {

    private static final Logger LOGGER = LogManager.getLogger(ConfigCenter.class);
    private static final Yaml YAML = new Yaml();

    /** 变量匹配正则 */
    private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\{([\\w.-]+)}");
    /** 配置中心代理对象 */
    private static final Config APP_CONFIG = ConfigService.getAppConfig();
    /** 配置及值Map */
    private static final ConcurrentMap<String, String> VALUE_MAP = new ConcurrentHashMap<>();
    /** 配置引用关系Map */
    private static final Multimap<String, String> REFERENCE_MAP = HashMultimap.create();
    /** 配置解析器Map */
    private static final ConcurrentMap<String, ConfigParser> PARSER_MAP = new ConcurrentHashMap<>();
    /** 配置清理器Map */
    private static final ConcurrentMap<String, ConfigCleaner> CLEANER_MAP = new ConcurrentHashMap<>();
    /** 配置解析结果Map */
    private static final ConcurrentMap<String, Object> RESULT_MAP = new ConcurrentHashMap<>();
    /** 空解析结果对象 */
    private static final Object NULL_RESULT = new Object();

    static {
        // 获取所有属性及值
        APP_CONFIG.getPropertyNames().forEach(e -> VALUE_MAP.put(e, APP_CONFIG.getProperty(e, null)));
        // 添加配置变更监听器 -> 单线程执行
        APP_CONFIG.addChangeListener(ConfigCenter::onConfigChanged);
    }

    /**
     * 简单获取配置的原始值
     *
     * @param k 配置key
     * @return 配置value
     */
    @NotNull
    public static Optional<String> getConfig(@NotNull String k) {
        return Optional.ofNullable(VALUE_MAP.get(k));
    }

    /**
     * 获取配置的值，并使用@{code p}解析成目标对象，配置值变更时，会自动调用@{code p}重新解析
     * 注意：相同key值得获取，不能使用不同的@{code p}，否则可能导致结果非预期
     *
     * @param k 配置key值
     * @param p 解析器(如果已经解析过且配置未变更不会重新解析)
     * @param <T>
     * @return
     */
    @NotNull
    public static Optional<Object> getConfig(@NotNull String k, @NotNull ConfigParser p) {
        // 如果结果存在，直接返回
        Object result = RESULT_MAP.get(k);
        if (result == null) {
            // 如果配置不存在，返回空
            String newValue = VALUE_MAP.get(k);
            if (newValue == null) {
                return Optional.empty();
            }
            // 注册配置解析器
            PARSER_MAP.putIfAbsent(k, p);
            // 解析配置值并存入结果对象Map
            onKeyChanged(k, false);
            // 重新检查结果是否存在
            result = RESULT_MAP.get(k);
        }
        return (result == null || result == NULL_RESULT) ? Optional.empty() : Optional.of(result);
    }

    /**
     * 获取配置的值，并使用@{code p}解析成目标对象，配置值变更时，会自动调用@{code p}重新解析，并会使用@{code c}清理老对象
     * 注意：相同key值得获取，不能使用不同的@{code p}和@{code c}，否则可能导致结果非预期
     *
     * @param k 配置key值
     * @param p 解析器(如果已经解析过且配置未变更不会重新解析)
     * @param c 清理器(重新解析成功后执行清理)
     * @return
     */
    public static Optional<Object> getConfig(@NotNull String k, @NotNull ConfigParser p, @NotNull ConfigCleaner c) {
        // 如果结果存在，直接返回
        Object result = RESULT_MAP.get(k);
        if (result == null) {
            // 如果配置不存在，返回空
            String newValue = VALUE_MAP.get(k);
            if (newValue == null) {
                return Optional.empty();
            }
            // 注册配置解析器和清理器
            PARSER_MAP.putIfAbsent(k, p);
            CLEANER_MAP.putIfAbsent(k, c);
            // 解析配置值并存入结果对象Map
            onKeyChanged(k, false);
            // 重新检查结果是否存在
            result = RESULT_MAP.get(k);
        }
        return (result == null || result == NULL_RESULT) ? Optional.empty() : Optional.of(result);
    }

    private static void onConfigChanged(ConfigChangeEvent event) {
        // 配置变更，配置值Map需要做相应删减，另外，引用这些属性的属性也需要重新解析
        // 删除属性时不理会Map中的相应key，因为：
        // 1. 属性量不会很大; 2. 为防止程序异常，属性被删除时，不清理结果对象Map; 3. 变量属性被删除时，可能会再次被添加，不能清理引用Map
        Set<String> changedKeys = new HashSet<>();
        event.changedKeys().forEach(key -> {
            ConfigChange change = event.getChange(key);
            LOGGER.info(change);
            switch (change.getChangeType()) {
            case ADDED:
            case MODIFIED:
                VALUE_MAP.put(key, change.getNewValue());
                changedKeys.add(key);
                changedKeys.addAll(REFERENCE_MAP.get(key));
                break;
            case DELETED:
                VALUE_MAP.remove(key);
                changedKeys.add(key);
                changedKeys.addAll(REFERENCE_MAP.get(key));
                break;
            default:
                break;
            }
        });
        // 重新解析有变更的属性值
        changedKeys.forEach(key -> onKeyChanged(key, true));
    }

    private static void onKeyChanged(@NotNull String key, boolean isConfigChanged) {
        // 不存在解析器或者值为null的时候, 直接返回
        ConfigParser parser = PARSER_MAP.get(key);
        String newValue = VALUE_MAP.get(key);
        if (parser == null || newValue == null) {
            return;
        }
        synchronized (ConfigCenter.class) {
            // 配置未变更时, 再次检查, 如果结果存在, 直接返回
            Object oldResult = RESULT_MAP.get(key);
            if (!isConfigChanged && oldResult != null) {
                return;
            }
            // 变量引用处理后执行解析
            // 解析结果存入结果Map(解析结果为null或者解析失败的情况，下次不再解析)
            Object newResult = doParse(key, newValue, parser);
            if (newResult == null) {
                RESULT_MAP.putIfAbsent(key, NULL_RESULT);
            } else {
                RESULT_MAP.put(key, newResult);
                doClean(key, oldResult, CLEANER_MAP.get(key));
            }
        }
    }

    @Nullable
    private static Object doParse(@NotNull String key, @NotNull String newValue, @NotNull ConfigParser parser) {
        String renderedValue = "null";
        boolean renderedFlag = false;
        try {
            Pair<Boolean, String> rendered = doReferenceRender(key, newValue);
            renderedFlag = rendered.getLeft();
            renderedValue = rendered.getRight();
            Object result = parser.parse(renderedValue);
            Map<String, String> msg;
            if (renderedFlag) {
                msg = ImmutableMap.of("key", key, "value", newValue, "rendered", renderedValue,
                        "result", String.valueOf(result));
            } else {
                msg = ImmutableMap.of("key", key, "value", newValue, "result", String.valueOf(result));
            }
            LOGGER.info("config parse finished:\n--->\n{}<---", YAML.dumpAsMap(msg));
            return result;
        } catch (Throwable e) {
            Map<String, String> msg;
            if (renderedFlag) {
                msg = ImmutableMap.of("key", key, "value", newValue, "rendered", renderedValue);
            } else {
                msg = ImmutableMap.of("key", key, "value", newValue);
            }
            LOGGER.error("config parse error:\n--->\n{}<---", YAML.dumpAsMap(msg), e);
            return null;
        }
    }

    private static void doClean(@NotNull String key, @Nullable Object oldResult, @Nullable ConfigCleaner cleaner) {
        if (oldResult == null || cleaner == null) {
            return;
        }
        Map<String, String> msg = ImmutableMap.of("key", key, "oldResult", String.valueOf(oldResult));
        try {
            // 防止未处理完的请求使用旧数据时异常
            Thread.sleep(3000);
            cleaner.clean(oldResult);
            LOGGER.info("config clean finished:\n--->\n{}<---", YAML.dumpAsMap(msg));
        } catch (Throwable e) {
            LOGGER.error("config clean error:\n--->\n{}<---", YAML.dumpAsMap(msg), e);
        }
    }

    /**
     * 替换配置中的变量为变量值
     * 约定：变量值中不能再包含变量(即不允许嵌套变量)
     *
     * @param key 配置key
     * @param value 配置原始值
     * @return 变量替换后的值
     */
    @NotNull
    private static Pair<Boolean, String> doReferenceRender(String key, String value) {
        boolean rendered = false;
        Set<String> referKeys = new HashSet<>();
        StringBuffer renderValue = new StringBuffer();
        Matcher varMatcher = VAR_PATTERN.matcher(value);
        while (varMatcher.find()) {
            rendered = true;
            String varName = varMatcher.group(1);
            String varValue = VALUE_MAP.get(varName);
            referKeys.add(varName);
            REFERENCE_MAP.put(varName, key);
            Preconditions.checkState(!key.equals(varName), "can not self-reference");
            Preconditions.checkState(varValue != null, "reference property not exist");
            varMatcher.appendReplacement(renderValue, Matcher.quoteReplacement(varValue));
        }
        varMatcher.appendTail(renderValue);
        REFERENCE_MAP.entries().removeIf(e -> key.equals(e.getValue()) && !referKeys.contains(e.getKey()));
        return Pair.of(rendered, renderValue.toString());
    }

    @FunctionalInterface
    public interface ConfigParser {

        /**
         * 解析配置，返回解析结果对象
         *
         * @param newValue 新值
         * @return
         * @throws Exception
         */
        Object parse(@NotNull String newValue) throws Exception;

    }

    @FunctionalInterface
    public interface ConfigCleaner {

        /**
         * 新value解析成功时，释放老对象资源
         *
         * @param oldResult 原解析结果(可能为null)
         * @throws Exception
         */
        void clean(@Nullable Object oldResult) throws Exception;

    }

}
